﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using CloudShot.Entities;
using CloudShot.Interfaces;
using CloudShot.Utils;

namespace CloudShot.Services
{
  internal class OpenWindowsService : IOpenWindowsService
  {
    private readonly Control _owner;
    private readonly IList<IntPtr> _ignoredWindowHandles;
    
    public OpenWindowsService(Control owner, params IntPtr[] ignoredWindowHandles)
    {
      _owner = owner;
      _ignoredWindowHandles = ignoredWindowHandles;
    }

    /// <summary>
    ///   Returns window rectangle in client coordinates
    /// </summary>
    /// <param name="screenPoint">Point in screen coordinates</param>
    /// <param name="truncateByCurrentScreen">Truncates all window bounds by current screen.</param>
    /// <returns>Window rectangle in client coordinates or <see cref="WindowInfo.Empty"/> if window not found.</returns>
    public WindowInfo GetWindowRectFromPoint(Point screenPoint, bool truncateByCurrentScreen = false)
    {
      Rectangle screenBounds = Screen.FromPoint(screenPoint).Bounds;

      foreach (IntPtr hWnd in EnumWindowsByZOrder())
      {
        WindowInfo window = GetWindowInfo(hWnd, screenBounds);

        if (window.IsEmpty)
          continue;

        if (window.Handle == IntPtr.Zero)
          continue;

        if (_ignoredWindowHandles != null && (_ignoredWindowHandles.Contains(window.Handle)))
          continue;

        if (window.ScreenBounds.Contains(screenPoint))
          return window;
      }

      return WindowInfo.Empty;
    }

    private static IEnumerable<IntPtr> EnumWindowsByZOrder()
    {
      IntPtr topWindowHandle = User32.GetTopWindow(IntPtr.Zero);

      if (topWindowHandle != IntPtr.Zero)
      {
        IntPtr nextWindow = topWindowHandle;

        while (nextWindow != IntPtr.Zero)
        {
          nextWindow = User32.GetWindow(nextWindow, User32.GetWindow_Cmd.GW_HWNDNEXT);
          yield return nextWindow;
        }
      }
    }

    /// <summary>
    /// Gets <see cref="WindowInfo"/> for the <paramref name="hWnd"/> window handle.
    /// </summary>
    /// <param name="hWnd">The window handle.</param>
    /// <param name="truncateRectangle">A <see cref="Rectangle"/> for truncate window size. In screen coordinates.</param>
    /// <returns>The <see cref="WindowInfo"/> for the <paramref name="hWnd"/> window handle.</returns>
    private WindowInfo GetWindowInfo(IntPtr hWnd, Rectangle truncateRectangle)
    {
      //save windows in screen coordinates
      if (User32.IsWindowVisible(hWnd))
      {
        var rect = new User32.RECT();
        User32.GetWindowRect(hWnd, ref rect);

        var bounds = new Rectangle(rect.Left, rect.Top, rect.Right - rect.Left, rect.Bottom - rect.Top);
        
        if (truncateRectangle != Rectangle.Empty)
          bounds = Rectangle.Intersect(bounds, truncateRectangle);
          
        return new WindowInfo
        {
          Handle = hWnd,
          ScreenBounds = bounds,
          ClientBounds = _owner.RectangleToClient(bounds)
        };
      }

      return WindowInfo.Empty;
    }
  }
}